#include "OgreMHX.h"
#include "OgreMaterialManager.h"
#include "OgreHardwareBufferManager.h"
#include "OgreMeshManager.h"
#include "OgreSubMesh.h"

#include <fstream>

#include <OgreLogManager.h>
#define LOG(M) Ogre::LogManager::getSingleton().logMessage(M)

// Load MHX from file
// TODO: resource loader functionality
OgreMHX::OgreMHX(std::string fileName)
{
	LOG("Reading MHX file " + fileName);

	if(file.parse(std::ifstream(fileName.c_str())))
	{
		ogreMeshes["Human"] = MHXToMesh(file.meshes["Human"]);

		unsigned short src, dest;
		if (!ogreMeshes["Human"]->suggestTangentVectorBuildParams(Ogre::VES_TANGENT, src, dest))
		{
			ogreMeshes["Human"]->buildTangentVectors(Ogre::VES_TANGENT, src, dest);
		}
	}
	else
		LOG("Error: Parsing failed!");

}

int findEntry(std::vector<Ogre::Vector2> &uvs, const Ogre::Vector2 &ref)
{
	for(int i = 0; i < uvs.size(); i++)
		if(uvs[i] == ref)
			return i;

	return -1;
}

// Create an Ogre mesh from the given MHX mesh.
// A submesh is created for each material.
// TODO: break into more functions to look nicer
Ogre::MeshPtr OgreMHX::MHXToMesh(const MHX::Mesh &mesh)
{
	Ogre::MeshPtr result = Ogre::MeshManager::getSingleton().createManual(mesh.name, "General");

	std::vector<std::vector<Ogre::Vector2>> vertexUV;
	std::vector<Ogre::Vector3> vertexNormal;
	std::vector<size_t> nVertexRefs;
	std::vector<size_t> idxOffset;

	for(int i = 0; i < mesh.verts.size(); i++)
	{
		vertexUV.push_back(std::vector<Ogre::Vector2>());
		vertexNormal.push_back(Ogre::Vector3(0, 0, 0));
		nVertexRefs.push_back(0);
		idxOffset.push_back(0);
	}

	// Allocate index buffer storage for each submesh
	// TODO: determine whether short is actually big enough
	unsigned short **faces = new unsigned short*[mesh.materials.size()];
	size_t *nIndex = new size_t[mesh.materials.size()];
	size_t *curIndex = new size_t[mesh.materials.size()];
	memset(nIndex, 0, sizeof(size_t) * mesh.materials.size());
	memset(curIndex, 0, sizeof(size_t) * mesh.materials.size());

	// Pre-compute face-related quantities, such as normals, and gather UV values to add to each vertex
	// TODO: figure out how many different texture coordinate sets are actually used
	for(int i = 0; i < mesh.faces.size(); i++)
	{
		const MHX::Face &f = mesh.faces[i];
		
		if(f.isQuad)
		{
			// Avoid repeating vertex if the UV value has already been used
			Ogre::Vector2 uv = Ogre::Vector2(mesh.uv[i][0], 1 - mesh.uv[i][1]);
			if(findEntry(vertexUV[f[0]], uv) == -1)
			{
				vertexUV[f[0]].push_back(uv);
				nVertexRefs[f[0]]++;
			}

			uv = Ogre::Vector2(mesh.uv[i][2], 1 - mesh.uv[i][3]);
			if(findEntry(vertexUV[f[1]], uv) == -1)
			{
				vertexUV[f[1]].push_back(uv);
				nVertexRefs[f[1]]++;
			}

			uv = Ogre::Vector2(Ogre::Vector2(mesh.uv[i][4], 1 - mesh.uv[i][5]));
			if(findEntry(vertexUV[f[2]], uv) == -1)
			{
				vertexUV[f[2]].push_back(uv);
				nVertexRefs[f[2]]++;
			}

			uv = Ogre::Vector2(Ogre::Vector2(mesh.uv[i][6], 1 - mesh.uv[i][7]));
			if(findEntry(vertexUV[f[3]], uv) == -1)
			{
				vertexUV[f[3]].push_back(uv);
				nVertexRefs[f[3]]++;
			}

			Ogre::Vector3 n1(mesh.verts[f[0]].v[0] - mesh.verts[f[2]].v[0],
							 mesh.verts[f[0]].v[1] - mesh.verts[f[2]].v[1],
							 mesh.verts[f[0]].v[2] - mesh.verts[f[2]].v[2]);

			Ogre::Vector3 n2(mesh.verts[f[1]].v[0] - mesh.verts[f[3]].v[0],
							 mesh.verts[f[1]].v[1] - mesh.verts[f[3]].v[1],
							 mesh.verts[f[1]].v[2] - mesh.verts[f[3]].v[2]);

			Ogre::Vector3 fN = n1.crossProduct(n2).normalisedCopy();

			vertexNormal[f[0]] += fN;
			vertexNormal[f[1]] += fN;
			vertexNormal[f[2]] += fN;
			vertexNormal[f[3]] += fN;
		}
		else
		{
			Ogre::Vector2 uv = Ogre::Vector2(mesh.uv[i][0], 1 - mesh.uv[i][1]);
			if(findEntry(vertexUV[f[0]], uv) == -1)
			{
				vertexUV[f[0]].push_back(uv);
				nVertexRefs[f[0]]++;
			}

			uv = Ogre::Vector2(mesh.uv[i][2], 1 - mesh.uv[i][3]);
			if(findEntry(vertexUV[f[1]], uv) == -1)
			{
				vertexUV[f[1]].push_back(uv);
				nVertexRefs[f[1]]++;
			}

			uv = Ogre::Vector2(Ogre::Vector2(mesh.uv[i][4], 1 - mesh.uv[i][5]));
			if(findEntry(vertexUV[f[2]], uv) == -1)
			{
				vertexUV[f[2]].push_back(uv);
				nVertexRefs[f[2]]++;
			}

			Ogre::Vector3 n1(mesh.verts[f[0]].v[0] - mesh.verts[f[1]].v[0],
							 mesh.verts[f[0]].v[1] - mesh.verts[f[1]].v[1],
							 mesh.verts[f[0]].v[2] - mesh.verts[f[1]].v[2]);

			Ogre::Vector3 n2(mesh.verts[f[1]].v[0] - mesh.verts[f[2]].v[0],
							 mesh.verts[f[1]].v[1] - mesh.verts[f[2]].v[1],
							 mesh.verts[f[1]].v[2] - mesh.verts[f[2]].v[2]);

			Ogre::Vector3 fN = n1.crossProduct(n2).normalisedCopy();

			vertexNormal[f[0]] += fN;
			vertexNormal[f[1]] += fN;
			vertexNormal[f[2]] += fN;
		}
	}

	for(int i = 1; i < mesh.verts.size(); i++)
		idxOffset[i] = idxOffset[i - 1] + nVertexRefs[i - 1] - 1;

	// Determine actual number of indexes for each submesh
	for(int i = 0; i < mesh.faces.size(); i++)
	{
		nIndex[mesh.faceProperties[i].materialIndex] += mesh.faces[i].isQuad ? 6 : 3;
	}

	for(int i = 0; i < mesh.materials.size(); i++)
		faces[i] = new unsigned short[nIndex[i]];

	// Set values for index buffer for each submesh
	for(int i = 0; i < mesh.faces.size(); i++)
	{
		size_t mIndex = mesh.faceProperties[i].materialIndex;
		size_t &idx   = curIndex[mIndex];
		const MHX::Face &f  = mesh.faces[i];

		if(f.isQuad)
		{
			// first triangle
			faces[mIndex][idx++] = f[0] + idxOffset[f[0]] + findEntry(vertexUV[f[0]], Ogre::Vector2(mesh.uv[i][0], 1 - mesh.uv[i][1]));
			faces[mIndex][idx++] = f[1] + idxOffset[f[1]] + findEntry(vertexUV[f[1]], Ogre::Vector2(mesh.uv[i][2], 1 - mesh.uv[i][3]));
			faces[mIndex][idx++] = f[2] + idxOffset[f[2]] + findEntry(vertexUV[f[2]], Ogre::Vector2(mesh.uv[i][4], 1 - mesh.uv[i][5]));

			// second triangle
			faces[mIndex][idx++] = f[2] + idxOffset[f[2]] + findEntry(vertexUV[f[2]], Ogre::Vector2(mesh.uv[i][4], 1 - mesh.uv[i][5]));
			faces[mIndex][idx++] = f[3] + idxOffset[f[3]] + findEntry(vertexUV[f[3]], Ogre::Vector2(mesh.uv[i][6], 1 - mesh.uv[i][7]));
			faces[mIndex][idx++] = f[0] + idxOffset[f[0]] + findEntry(vertexUV[f[0]], Ogre::Vector2(mesh.uv[i][0], 1 - mesh.uv[i][1]));
		}
		else
		{
			faces[mIndex][idx++] = f[0] + idxOffset[f[0]] + findEntry(vertexUV[f[0]], Ogre::Vector2(mesh.uv[i][0], 1 - mesh.uv[i][1]));
			faces[mIndex][idx++] = f[1] + idxOffset[f[1]] + findEntry(vertexUV[f[1]], Ogre::Vector2(mesh.uv[i][2], 1 - mesh.uv[i][3]));
			faces[mIndex][idx++] = f[2] + idxOffset[f[2]] + findEntry(vertexUV[f[2]], Ogre::Vector2(mesh.uv[i][4], 1 - mesh.uv[i][5]));
		}
	}

	int nTotalVerts = 0;
	for(int i = 0; i < mesh.verts.size(); i++)
		nTotalVerts += nVertexRefs[i];

	// allocate room for position, UV values, and normals
	// (UV = 2, normals = 3, position = 3, total = 8)
	float *vertices = new float[(nTotalVerts) * 8];

	Ogre::AxisAlignedBox bounds;
	float radius = 0.0f;
	size_t idx = 0;
	for(int i = 0; i < mesh.verts.size(); i++)
	{
		for(int j = 0; j < vertexUV[i].size(); j++)
		{
			vertices[idx++] = mesh.verts[i][0];
			vertices[idx++] = mesh.verts[i][1];
			vertices[idx++] = mesh.verts[i][2];

			Ogre::Vector3 normal = vertexNormal[i].normalisedCopy();

			vertices[idx++] = normal[0];
			vertices[idx++] = normal[1];
			vertices[idx++] = normal[2];

			// TODO: multiple texture coordinate sets?
			vertices[idx++] = vertexUV[i][j][0];
			vertices[idx++] = vertexUV[i][j][1];
		}

		Ogre::Vector3 point(mesh.verts[i][0], mesh.verts[i][1], mesh.verts[i][2]);
		bounds.merge(point);
		radius = std::max(radius, point.length());
		vertexUV[i].clear();
	}

	vertexUV.clear();
	vertexNormal.clear();

	result->sharedVertexData = new Ogre::VertexData();
	result->sharedVertexData->vertexCount = nTotalVerts;
	Ogre::VertexDeclaration *decl = result->sharedVertexData->vertexDeclaration;
	size_t offset = 0;
	decl->addElement(0, offset, Ogre::VET_FLOAT3, Ogre::VES_POSITION);
	offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3);
	decl->addElement(0, offset, Ogre::VET_FLOAT3, Ogre::VES_NORMAL);
	offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3);
	decl->addElement(0, offset, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES);
	offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2);

	Ogre::HardwareVertexBufferSharedPtr vbuf = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(offset, result->sharedVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);

	vbuf->writeData(0, vbuf->getSizeInBytes(), vertices, true);

	Ogre::VertexBufferBinding *bind = result->sharedVertexData->vertexBufferBinding;
	bind->setBinding(0, vbuf);

	for(int i = 0; i < mesh.materials.size(); i++)
	{
		if(nIndex[i] == 0)
			continue;

		Ogre::HardwareIndexBufferSharedPtr ibuf = Ogre::HardwareBufferManager::getSingleton().
		createIndexBuffer(
		Ogre::HardwareIndexBuffer::IT_16BIT, 
		nIndex[i], 
		Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);

		ibuf->writeData(0, ibuf->getSizeInBytes(), faces[i], true);

		Ogre::SubMesh *sm = result->createSubMesh();
		sm->useSharedVertices = true;
		sm->operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST;
		sm->indexData->indexBuffer = ibuf;
		sm->indexData->indexCount = nIndex[i];
		sm->indexData->indexStart = 0;

		sm->setMaterialName("MHX/" + mesh.name + "/" + mesh.materials[i].name);
	}

	result->load();
	result->_setBounds(bounds);
	result->_setBoundingSphereRadius(radius);

	// TODO: check for leaks


	return result;
}

Ogre::ManualObject *OgreMHX::MHXToObject(const MHX::Mesh &mesh)
{
	Ogre::ManualObject *object = new Ogre::ManualObject("OgreMHXObject");
	object->estimateVertexCount(mesh.verts.size());
	object->estimateIndexCount(4 * mesh.faces.size());

	std::stringstream msg;
	msg << "nVertices = " << mesh.verts.size() << " nuv = " << mesh.uv.size() << " nFaces = " << mesh.faces.size();
	LOG(msg.str());

	std::vector<std::vector<Ogre::Vector2>> vertexUV;
	std::vector<Ogre::Vector3> vertexNormal;

	for(int i = 0; i < mesh.verts.size(); i++)
	{
		vertexUV.push_back(std::vector<Ogre::Vector2>());
		vertexNormal.push_back(Ogre::Vector3(0, 0, 0));
	}

	// Pre-compute face-related quantities, such as normals, and gather UV values to add to each vertex
	size_t faceIndex = 0;
	for(MHX::Faces::const_iterator i = mesh.faces.begin(); i != mesh.faces.end(); i++)
	{
		if(i->isQuad)
		{
			//object->quad(i->ind[0], i->ind[1], i->ind[2], i->ind[3]);
			vertexUV[i->ind[0]].push_back(Ogre::Vector2(mesh.uv[faceIndex][0], 1 - mesh.uv[faceIndex][1]));
			vertexUV[i->ind[1]].push_back(Ogre::Vector2(mesh.uv[faceIndex][2], 1 - mesh.uv[faceIndex][3]));
			vertexUV[i->ind[2]].push_back(Ogre::Vector2(mesh.uv[faceIndex][4], 1 - mesh.uv[faceIndex][5]));
			vertexUV[i->ind[3]].push_back(Ogre::Vector2(mesh.uv[faceIndex][6], 1 - mesh.uv[faceIndex][7]));

			Ogre::Vector3 n1(mesh.verts[i->ind[0]].v[0] - mesh.verts[i->ind[2]].v[0],
							 mesh.verts[i->ind[0]].v[1] - mesh.verts[i->ind[2]].v[1],
							 mesh.verts[i->ind[0]].v[2] - mesh.verts[i->ind[2]].v[2]);

			Ogre::Vector3 n2(mesh.verts[i->ind[1]].v[0] - mesh.verts[i->ind[3]].v[0],
							 mesh.verts[i->ind[1]].v[1] - mesh.verts[i->ind[3]].v[1],
							 mesh.verts[i->ind[1]].v[2] - mesh.verts[i->ind[3]].v[2]);

			Ogre::Vector3 fN = n1.crossProduct(n2).normalisedCopy();

			vertexNormal[i->ind[0]] += fN;
			vertexNormal[i->ind[1]] += fN;
			vertexNormal[i->ind[2]] += fN;
			vertexNormal[i->ind[3]] += fN;
		}
		else
		{
			//object->triangle(i->ind[0], i->ind[1], i->ind[2]);
			vertexUV[i->ind[0]].push_back(Ogre::Vector2(mesh.uv[faceIndex][0], 1 - mesh.uv[faceIndex][1]));
			vertexUV[i->ind[1]].push_back(Ogre::Vector2(mesh.uv[faceIndex][2], 1 - mesh.uv[faceIndex][3]));
			vertexUV[i->ind[2]].push_back(Ogre::Vector2(mesh.uv[faceIndex][4], 1 - mesh.uv[faceIndex][5]));

			Ogre::Vector3 n1(mesh.verts[i->ind[0]].v[0] - mesh.verts[i->ind[1]].v[0],
							 mesh.verts[i->ind[0]].v[1] - mesh.verts[i->ind[1]].v[1],
							 mesh.verts[i->ind[0]].v[2] - mesh.verts[i->ind[1]].v[2]);

			Ogre::Vector3 n2(mesh.verts[i->ind[1]].v[0] - mesh.verts[i->ind[2]].v[0],
							 mesh.verts[i->ind[1]].v[1] - mesh.verts[i->ind[2]].v[1],
							 mesh.verts[i->ind[1]].v[2] - mesh.verts[i->ind[2]].v[2]);

			Ogre::Vector3 fN = n1.crossProduct(n2).normalisedCopy();

			vertexNormal[i->ind[0]] += fN;
			vertexNormal[i->ind[1]] += fN;
			vertexNormal[i->ind[2]] += fN;
		}

		faceIndex++;

	}

	// Define vertices first
	object->setDynamic(true);
	object->begin("MHX_default", Ogre::RenderOperation::OT_TRIANGLE_LIST);
	for(int i = 0; i < mesh.verts.size(); i++)
	{
		object->position(mesh.verts[i].v[0], mesh.verts[i].v[1], mesh.verts[i].v[2]);
		object->normal(vertexNormal[i].normalisedCopy());

		// Add UV coordinates for vertex
		for(int j = 0; j < vertexUV[i].size(); j++)
		{
			object->textureCoord(vertexUV[i][j]);
		}

		vertexUV[i].clear();
	}

	vertexUV.clear();
	vertexNormal.clear();
	object->end();

	LOG("got here");

	// Finally, construct mesh, using the appropriate material for each face
	for(int mIndex = 0; mIndex < mesh.materials.size(); mIndex++)
	{
		object->begin("MHX/" + mesh.name + "/" + mesh.materials[mIndex].name, Ogre::RenderOperation::OT_TRIANGLE_LIST);

		LOG("hello");
		size_t faceIndex = 0;
		for(MHX::Faces::const_iterator i = mesh.faces.begin(); i != mesh.faces.end(); i++)
		{
			if(mesh.faceProperties[faceIndex].materialIndex == mIndex)
			{
				if(i->isQuad)
					object->quad(i->ind[0], i->ind[1], i->ind[2], i->ind[3]);
				else
					object->triangle(i->ind[0], i->ind[1], i->ind[2]);
			}

			faceIndex++;
		}

		object->end();
	}

	return object;
}

Ogre::MeshPtr OgreMHX::getOgreMesh(std::string meshName)
{
	return ogreMeshes[meshName];
}

//const OgreMHX::Strings& OgreMHX::getMeshNames() const
//{
	//return meshNames;
//}